/** @file genxpath.c
* Implements all functions declared in genxpath.h
*
* @author Victor Petrov <victor_petrov@harvard.edu>
* @date   Feb 26, 2011
* @cond
*------------------------------------------------------------------------------
* LEGAL:
*------------------------------------------------------------------------------
*
* Copyright (c) 2011, The President and Fellows of Harvard College
* Copyright (c) 2011, The Neuroinformatics Research Group at Harvard University
* All rights reserved.
*
* License: New BSD License   <http://creativecommons.org/licenses/BSD/>
*-------------------------
* Redistribution and use in source and binary forms, with or without 
* modification, are permitted provided that the following conditions are met:
*
*	Redistributions of source code must retain the above copyright notice, 
*	this list of conditions and the following disclaimer.
*
*	Redistributions in binary form must reproduce the above copyright notice, 
*	this list of conditions and the following disclaimer in the documentation
*	and/or other materials provided with the distribution.
*
*	Neither the name of the Neuroinformatics Research Group at Harvard, nor 
*	the names of its contributors may be used to endorse or promote products 
*	derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
* POSSIBILITY OF SUCH DAMAGE.
*------------------------------------------------------------------------------
* vim: ts=4
*------------------------------------------------------------------------------
* @endcond
*/

#ifdef PHPEXT
#include "php.h"
#include "php_vxp.h"
#endif

#include "genxpath.h"

#include <assert.h>
#include <stdio.h>
#include <string.h>

#include "errors.h"

static void _genXPath10(const struct Node* node, zval* siblings, 
                        unsigned int level TSRMLS_DC);
static void _genXPath21(const struct Node* node, zval* siblings, 
                        zval* parent, zval* parent_children,
                        unsigned int level, struct MemBlockInfo* mem TSRMLS_DC);
static zval* _genNode(const char* name, zval* result, int level TSRMLS_DC);
static zval* _genValueNode(const char* name, const char* value, zval* result, 
                        unsigned int level TSRMLS_DC);
static zval* _addChildrenProperty(zval* val TSRMLS_DC);
static zval* _addChild(zval* parent TSRMLS_DC);
static char* _concatValueNodes(struct Node* start, int names, 
                               struct MemBlockInfo* TSRMLS_DC);

static zval* _genQuotedValueNode(const char* name, const char* value,
                                 zval* siblings, unsigned int level, char quote
                                 TSRMLS_DC);

void getXPath(struct Node* node, zval* result, enum XPathVersion version, 
              struct MemBlockInfo* mem TSRMLS_DC)
{
    switch (version)
    {
        case XPATH_10: getXPath10(node,result TSRMLS_CC); break;
        case XPATH_20: getXPath20(node,result TSRMLS_CC); break;
        case XPATH_21: getXPath21(node,result,mem TSRMLS_CC); break;
        default:       getXPath10(node,result TSRMLS_CC); break;
    }
}

void getNode(struct Node* node, zval* result TSRMLS_DC)
{
	getXPath10(node, result TSRMLS_CC);
}

void getXPath10(struct Node* node, zval* result TSRMLS_DC)
{
    zval* children=NULL;
	assert(node);

    add_assoc_string(result,"name","START",1);

    node=nodeGetFirstSibling(node);

    children=_addChildrenProperty(result TSRMLS_CC);

    while (node)
    {
        _genXPath10(node,children,0 TSRMLS_CC);
        node=node->right;
    }

}


void getXPath20(struct Node* node, zval* result TSRMLS_DC)
{
   php_error_docref(NULL TSRMLS_CC, E_ERROR, 
                    "ERROR: XPath 2.0 generator method is not implemented.");
}

void getXPath21(struct Node* node, zval* result, struct MemBlockInfo* mem TSRMLS_DC)
{
    zval* children=NULL;

    assert(node);

    add_assoc_string(result,"name","START",1);

    node=nodeGetFirstSibling(node);

    children=_addChildrenProperty(result TSRMLS_CC);

    while (node)
    {
        _genXPath21(node,children, result, children, 0, mem TSRMLS_CC);
        node=node->right;
    }

}

static void _genXPath10(const struct Node* node, zval* siblings,
                        unsigned int level TSRMLS_DC)
{
    zval* children=NULL;
    zval* child=NULL;

	assert(node);

    switch(node->type)
    {
        case NODE_STRING: if (node->extra.quotes)
                              child=_genQuotedValueNode(node->name,
                                                        (char*)node->data.p,
                                                        siblings,
                                                        level,
                                                        node->extra.quotes
                                                        TSRMLS_CC);
                          else
                              child=_genValueNode(node->name,
                                                  (char*)node->data.p,
                                                  siblings,
                                                  level TSRMLS_CC);
                          return;
        case NODE_TOKEN:  child=_addChild(siblings TSRMLS_CC);
                          add_assoc_string(child,"value",(char*)node->name,1);
                          add_assoc_bool(child,"token",1);
                          return;
        default: child=_genNode(node->name,siblings,level TSRMLS_CC);
    }

	if (node->children>0)
	{
        children=_addChildrenProperty(child TSRMLS_CC);
		node=node->first;

		while (node)
		{
			_genXPath10(node,children,level+1 TSRMLS_CC);
			node=node->right;
		}

	}

}

static char* _concatValueNodes(struct Node* start, int names, struct MemBlockInfo* mem TSRMLS_DC)
{
    char* buffer=NULL;
    char* string=NULL;
    int buflen=1;   /* \0 */
    int stringlen=0;
    struct Node* node=start;

    /* Iterate over all nodes */
    while (node)
    {
        string=NULL;

        /* Determine whether we need to concatenate the value of
           the node, or the name of the node */
        if (node->type==NODE_STRING)
            string=(char*)node->data.p;
        else
        if (names)
            string=(char*)node->name;

        /* If there should be something to contenate, then ... */
        if (string)
        {
            stringlen=strlen(string);

            /* If there indeed is something to work with, then ...*/
            if (stringlen)
            {
                /* Grow the buffer */
                buffer=erealloc(buffer,buflen+stringlen);

                if (!buffer)
                    memerror(buflen+1,__func__,__FILE__,__LINE__,mem);

                /* Copy the data. We use memcpy instead of strcpy
                   because we already know the length of the string */
                memcpy(buffer+buflen-1,string,stringlen);

                /* Store the new buffer length */
                buflen+=stringlen;

                /* Make sure the buffer is zero-terminated */
                buffer[buflen-1]='\0';
            }
        }

        /* Next please */
        node=node->right;
    }

    /* Return the new string. Might be NULL */
    return buffer;
}

static zval* _genNode(const char* name, zval* siblings, int level TSRMLS_DC)
{
    zval* result=NULL;

    result=_addChild(siblings TSRMLS_CC);
    add_assoc_string(result,"name",(char*)name,1);

    return result;
}

static zval* _genValueNode(const char* name, const char* value, zval* siblings,
                           unsigned int level TSRMLS_DC)
{
    zval* result=NULL;

    result=_addChild(siblings TSRMLS_CC);
    add_assoc_string(result,"name",(char*)name,1);
    add_assoc_string(result,"value",(char*)value,1);

    return result;
}

static zval* _genQuotedValueNode(const char* name, const char* value,
                                 zval* siblings, unsigned int level, char quote
                                 TSRMLS_DC)
{
    zval* result=NULL;
    char szQuote[]={quote,'\0'};

    result=_genValueNode(name,value,siblings,level TSRMLS_CC);

    add_assoc_string(result,"quote",szQuote,1);/* it's safe to cast */

    return result;
}

static void _genXPath21(const struct Node* node, zval* siblings, 
                        zval* parent, zval* parent_children,
                        unsigned int level, struct MemBlockInfo* mem TSRMLS_DC)
{
    char* value=NULL;
    zval* children=NULL;
    zval* child=NULL;

	assert(node);

    if (node->type==NODE_STRING)
    {
        if (isRule(node->parent,NodeTest) &&
           (isToken(node->parent->first,PROCESSING_INSTRUCTION)))
            child=_genQuotedValueNode("StringLiteral",
                                      (char*)node->data.p,siblings,
                                      level+1,
                                      node->extra.quotes
                                      TSRMLS_CC);
        else
            child=_genQuotedValueNode("StringLiteral",
                                      (char*)node->data.p,siblings,
                                      level,
                                      node->extra.quotes
                                      TSRMLS_CC);
    }
    else
    if (node->type==NODE_RULE)
    {
        switch (node->data.nt)
        {
            case AbbreviatedStep:
                                if (node->stepKind==STEP_REVERSE)
                                    child=_genNode("AbbrevReverseStep",siblings,level TSRMLS_CC);
                                else
                                if (node->stepKind==STEP_CURRENT)
                                    child=_genValueNode("ContextItemExpr",
                                                  node->first->name,
                                                  siblings,
                                                  level TSRMLS_CC);

                                return;

            case AbsoluteLocationPath: if (!isRule(node->first,
                                           AbbreviatedAbsoluteLocationPath))
                                       {
                                           child=_genValueNode("Slash","/",
                                                         siblings,level TSRMLS_CC);
                                       }

                                       level--;
                                       children=parent_children;
                                       break;

            case AndExpr:
            case OrExpr:
            case MultiplicativeExpr:
            case AdditiveExpr:  child=_genValueNode(node->name,
                                              node->first->right->name,
                                              siblings,
                                              level TSRMLS_CC);
                                break;
            case Arguments: level--; children=parent_children; break;
            case Argument:  level--; children=parent_children; break;
            case AxisSpecifier:
                                /* AbbrevForwardStep: ('@')? NodeTest */
                                if (isToken(node->first,AT_SIGN))
                                {
                                    child=_genValueNode("AbbrevForwardStep","@",
                                                  siblings,level TSRMLS_CC);
                                    return;
                                }

                                if (node->stepKind==STEP_FORWARD)
                                    child=_genValueNode("ForwardAxis",
                                                  node->first->first->name,
                                                  siblings,
                                                  level TSRMLS_CC);
                                else
                                if (node->stepKind==STEP_REVERSE)
                                    child=_genValueNode("ReverseAxis",
                                                  node->first->first->name,
                                                  siblings,
                                                  level TSRMLS_CC);
                                else
                                    child=_genNode("<UNKNOWN>",siblings,level TSRMLS_CC);

                                return;

            case Expr:       if (isRule(node->parent,XPath)  ||
                                 isToken(node->left,LEFT_PAREN))
                                    child=_genNode("Expr",siblings,level TSRMLS_CC); 
                             break;

            case FilterExpr: if (!isRule(node->parent,FilterExpr))
                                 child=_genNode("StepExpr",siblings,level TSRMLS_CC); 
                             else
                             {
                                 level--;
                                 children=parent_children;
                             }
                             break;

            case FunctionCall: child=_genNode("FunctionCall",siblings,level TSRMLS_CC);

                             children=_addChildrenProperty(child TSRMLS_CC);
                            /* function name */
                            _genXPath21(node->first,children,child,siblings,level+1, mem TSRMLS_CC);
                            
                            if (node->children>1)
                            {
                                /* Arguments */
                                node=node->first->right;

                                while (node)
                                {
                                    _genXPath21(node,children, child, siblings, level, mem TSRMLS_CC);
                                    node=node->right;
                                }
                            }

                            return;

            case NodeTest:
                             /* AbbrevForwardStep: NodeTest */
                            if (isRule(node->parent,Step) && (!node->left))
                            {
                                child=_genNode("AbbrevForwardStep",siblings,level TSRMLS_CC);
                                level++;
                                children=_addChildrenProperty(child TSRMLS_CC);
                                child=_genNode(node->name,children,level TSRMLS_CC); 
                                children=NULL;
                            }
                            else
                             /* AbbrevForwardStep: '@' NodeTest */
                            if (isRule(node->left,AxisSpecifier) &&
                                isToken(node->left->first,AT_SIGN))
                            {
                                level++;
                                child=_genNode(node->name,siblings,level TSRMLS_CC); 
                            }

                            break;

            case QName:     
                            /* Concatenate the values of all child nodes.
                               Use the node name if it doesn't have a value */
                            value=_concatValueNodes(node->first,1, mem TSRMLS_CC);

                            if (isRule(node->parent,FunctionName))
                                child=_genValueNode("FunctionQName",value,
                                              siblings,level TSRMLS_CC); 
                            else 
                                child=_genValueNode("QName",value,siblings,level TSRMLS_CC); 

                            /* Release the concatenated value */
                            efree(value);

                            return;

            case PathExpr:  if (node->extra.isPath)
                                child=_genNode("PathExpr",siblings,level TSRMLS_CC); 
                            else
                            {
                                level--;
                                children=parent_children;
                            }
                            break;

            case PredicateExpr: child=_genNode("Expr",siblings,level TSRMLS_CC);
                                level--;
                                break;

            case Predicates: child=_genNode("PredicateList",siblings,level TSRMLS_CC);
                             break;

            case PrimaryExpr: if (isToken(node->first,LEFT_PAREN))
                                  child=_genNode("ParenthesizedExpr",siblings,level TSRMLS_CC);
                              else
                              {
                                  level--;
                                  children=parent_children;
                              }
                              break;

            case EqualityExpr:
            case RelationalExpr: 
                            child=_genValueNode("ComparisonExpr",
                                          node->first->right->name,
                                          siblings,
                                          level TSRMLS_CC);
                            break;

            case Step:      child=_genNode("StepExpr",siblings,level TSRMLS_CC); 
                            break;

            case UnaryExpr: if (!isRule(node->parent,UnaryExpr))
                                child=_genNode("UnaryExpr",siblings,level TSRMLS_CC); 
                            else
                            {
                                level--;
                                children=parent_children;
                            }
                            break;

            case UnionExpr:  child=_genValueNode(node->name,
                                           node->first->right->name,
                                           siblings,
                                           level TSRMLS_CC);
                                break;
            case VariableReference: child=_genNode("VarName",siblings,level TSRMLS_CC); 
                                    break;

            /* ignored */
            case AbbreviatedAbsoluteLocationPath:
            case AbbreviatedRelativeLocationPath:
            case FunctionName:
            case LocationPath:
            case RelativeLocationPath:
                            /* Decrease level for this node */
                            level--;
                            children=parent_children;
                            break;
            default:    
                        child=_genNode(node->name,siblings,level TSRMLS_CC);
        }
    }
    else
    if (node->type==NODE_TOKEN)
    {
        switch (node->data.t)
        {
            case ANY:           child=_genValueNode("Wildcard",node->name,
                                              siblings,level TSRMLS_CC);
                                break;


            case MINUS:         if (isRule(node->parent,UnaryExpr))
                                    child=_genValueNode("Minus",node->name,
                                                  siblings,level TSRMLS_CC);
                                break;

            case NUMBER:        /* XPath 2.1 has a DecimalLiteral type */
                                if (strstr(node->name,"."))
                                    child=_genValueNode("DecimalLiteral",node->name,
                                                  siblings,level TSRMLS_CC);
                                else
                                    child=_genValueNode("IntegerLiteral",node->name,
                                                  siblings,level TSRMLS_CC);
                                break;

            case SLASH_SLASH:   child=_genValueNode("SlashSlash",node->name,
                                              siblings,level TSRMLS_CC);
                                break;

            case TEXT:          child=_genNode("TextTest",siblings,level TSRMLS_CC);
                                break;

            case NODE:          child=_genNode("AnyKindTest",siblings,level TSRMLS_CC);
                                break;

            case COMMENT:       child=_genNode("CommentTest",siblings,level TSRMLS_CC);
                                break;

            case PROCESSING_INSTRUCTION: child=_genNode("PITest",siblings,level TSRMLS_CC);
                                break;


            default:            /* skip */ ;
        }
    }
    else
        child=_genNode(node->name,siblings,level TSRMLS_CC);

	if (node->children>0)
	{
        if (!child)
            children=siblings;
        else
        if (!children)
            children=_addChildrenProperty(child TSRMLS_CC);

		node=node->first;

		while (node)
		{
			_genXPath21(node,children,child,siblings,level+1, mem TSRMLS_CC);
			node=node->right;
		}
	}
}

static zval* _addChildrenProperty(zval* val TSRMLS_DC)
{
    zval* children=NULL;

    MAKE_STD_ZVAL(children);
    array_init(children);
    add_assoc_zval(val,"children",children);

    return children;
}

static zval* _addChild(zval* parent TSRMLS_DC)
{
    zval* child=NULL;
    
    MAKE_STD_ZVAL(child);
    array_init(child);
    add_next_index_zval(parent,child);

    return child;
}
